#include "camera2d.h"
#include <glm/gtc/matrix_transform.hpp>
#include "engine/core/logging.h"

namespace kanon
{
    Camera2d::Camera2d()
    {
        pos = glm::vec2(0.0, 0.0);
        scale = 1.0f;
        rotation = 0;
        xmin = -1.0f;
        xmax = 1.0f;
        ymin = -1.0f;
        ymax = 1.0f;
        windowWidth = 0;
        windowHeight = 0;
    }

    void Camera2d::SetWindowSize(uint32_t windowWidth, uint32_t windowHeight)
    {
        this->windowWidth = windowWidth;
        this->windowHeight = windowHeight;

        if (windowWidth > windowHeight)
        {
            float r = windowWidth * 1.0f / windowHeight;
            ymin = -1.0f;
            ymax = 1.0f;
            xmin = -r;
            xmax = r;
        }
        else
        {
            float r = windowHeight * 1.0f / windowWidth;
            xmin = -1.0f;
            xmax = 1.0f;
            ymin = -r;
            ymax = r;
        }
    }

    void Camera2d::Move(float dx, float dy)
    {
        pos.x += dx;
        pos.y += dy;
    }

    void Camera2d::Rotate(float dt)
    {
        rotation += dt;
    }

    void Camera2d::Zoom(float dz)
    {
        scale += dz;
    }

    glm::mat4 Camera2d::GetViewMatrix() const
    {
        glm::mat4 m(1.0f);
#if 0
        m = glm::translate(m, glm::vec3(-pos.x, -pos.y, 0.0f));
        m = glm::rotate(m, -rotation, glm::vec3(0.0, 0.0, 1.0f));
        m = glm::scale(m, glm::vec3(scale, scale, 1.0f));
#else
        m = glm::scale(m, glm::vec3(scale, scale, 1.0f));
        m = glm::rotate(m, rotation, glm::vec3(0.0f, 0.0f, -1.0f));
        m = glm::translate(m, glm::vec3(pos.x, pos.y, 0.0f));
#endif
        return m;
    }

    glm::mat4 Camera2d::GetProjectionMatrix(uint32_t windowWidth, uint32_t windowHeight) const
    {
#if 0
        float left = - 1.0f * windowWidth / (2.0f * scale);
        float right = windowWidth / (2.0f * scale);
        float bottom = windowHeight / (2.0f * scale);
        float top = - 1.0f * windowHeight / (2.0f * scale);
        float near = -1.0f;
        float far = 1.0f;
        return glm::ortho(left, right, bottom, top, near, far);
#else
        return glm::ortho(xmin, xmax, ymin, ymax, -1.0f, 1.0f);
#endif
    }

    glm::vec2 Camera2d::ScreenToWorld(float x, float y)
    {
        // screen space to world space
        float sx = x / windowWidth * (xmax - xmin) + xmin;
        float sy = y / windowHeight * (ymax - ymin) + ymin;

        glm::mat4 m(1.0f);
        m = glm::translate(m, glm::vec3(-pos.x, -pos.y, 0.0f));
        m = glm::rotate(m, -rotation, glm::vec3(0.0, 0.0, -1.0f));
        m = glm::scale(m, glm::vec3(1.0f / scale, 1.0f / scale, 1.0f));

        glm::vec4 svec = { sx, sy, 0.0f, 1.0f };
        svec = m * svec;

        // Logger& logger = Logger::getInstance();
        // logger.debug("sx: %f, sy: %f\n", sx, sy);
        // logger.debug("svec.x: %f, svec.y: %f\n", svec.x, svec.y);

        return glm::vec2(svec.x, svec.y);
    }

    glm::vec2 Camera2d::WorldToScreen(float x, float y)
    {
        // world space to screen space
        glm::mat4 view = this->GetViewMatrix();
        glm::mat4 proj = this->GetProjectionMatrix();
        glm::vec4 world_pos(x, y, 0.0f, 1.0f);
        glm::vec4 screen_pos = view * proj * world_pos;
        return glm::vec2(screen_pos.x, screen_pos.y);
    }
}
